﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using System.ComponentModel.Design;

namespace Tsanie.ShellExtension {

    #region - Structs/Enums -

    public enum PSP_FLAGS {
        PSP_DEFAULT = 0x0,
        PSP_DLGINDIRECT = 0x1,
        PSP_USEHICON = 0x2,
        PSP_USEICONID = 0x4,
        PSP_USETITLE = 0x8,
        PSP_RTLREADING = 0x10,
        PSP_HASHELP = 0x20,
        PSP_USEREFPARENT = 0x40,
        PSP_USECALLBACK = 0x80,
        PSP_PREMATURE = 0x400,
        PSP_HIDEHEADER = 0x800,
        PSP_USEHEADERTITLE = 0x1000,
        PSP_USEHEADERSUBTITLE = 0x2000
    }

    public enum PSPCALLBACKMESSAGES {
        PSPCB_RELEASE = 1,
        PSPCB_CREATE = 2
    }

    public delegate int WndProc(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam);

    [return: MarshalAs(UnmanagedType.Bool)]
    public delegate Boolean PropertySheetCallback(IntPtr hWnd, PSPCALLBACKMESSAGES Msg, ref PROPSHEETPAGE Page);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct PROPSHEETPAGE {
        public int dwSize;
        public PSP_FLAGS dwFlags;
        public int hInstance;
        public IntPtr pResource;
        public IntPtr hIcon;
        public String pszTitle;
        [MarshalAs(UnmanagedType.FunctionPtr)]
        public WndProc pfnDlgProc;
        public int lParam;
        [MarshalAs(UnmanagedType.FunctionPtr)]
        public PropertySheetCallback pfnCallback;
        public int pcRefParent;
    }

    public struct DLGTEMPLATE {
        public int Style;
        public int dwExtendedStyle;
        public short cdit;
        public short x;
        public short y;
        public short cx;
        public short cy;
        public short Menu;
        [MarshalAs(UnmanagedType.LPStr, SizeConst = 7)]
        public String Class;
        public int Caption;
    }

    public enum PSNOTIFYCODES {
        PSN_FIRST = (-200),
        PSN_LAST = (-299),
        PSN_SETACTIVE = (-200),
        PSN_KILLACTIVE = (-201),
        PSN_APPLY = (-202),
        PSN_RESET = (-203),
        PSN_HELP = (-205),
        PSN_WIZBACK = (-206),
        PSN_WIZNEXT = (-207),
        PSN_WIZFINISH = (-208),
        PSN_QUERYCANCEL = (-209),
        PSN_GETOBJECT = (-210)
    }

    public struct PSHNOTIFY {
        public IntPtr hwndFrom;
        public int idFrom;
        public PSNOTIFYCODES code;
        public IntPtr lParam;
    }

    #endregion

    [Designer("System.Windows.Forms.Design.DocumentDesigner, System.Design", typeof(IRootDesigner))]
    [DesignerCategory("Form")]
    public class PropertySheetBase : ScrollableControl, IShellExtInit, IShellPropSheetExt {

        #region - 私有字段 -

        private FileCollection m_Files;
        private bool m_ShowHelpButton;
        private Bitmap m_Icon;
        private PROPSHEETPAGE m_PropSheet;
        private IntPtr m_SheetHandle;
        private IntPtr m_DlgHandle;
        private bool m_IsXP = false;

        #endregion

        #region - 构造 -

        public PropertySheetBase()
            : base() {
            //base.ResizeRedraw = true;
            base.Size = new Size(341, 408);
            //base.BackColor = Color.Transparent;
            //base.ForeColor = Color.FromKnownColor(KnownColor.WindowText);
            if (Environment.OSVersion.Platform == PlatformID.Win32NT)
                if (Environment.OSVersion.Version.Major > 5 && Environment.OSVersion.Version.Minor > 0) {
                    m_IsXP = true;
#if TRACE
                    Common.OutputDebugString("PropertySheetBase(void):: system is greater than XP.");
#endif
                    if (PropertySheetBase.IsThemeActive()) {
                        IntPtr hTheme = OpenThemeData(this.Handle, VSCLASS_TAB);
                        int pColor;
                        GetThemeColor(hTheme, TABP_BODY, TIS_NORMAL, TMT_GLOWCOLOR, out pColor);
                        Common.OutputDebugString("pColor: #{0:x6}", pColor);
                        CloseThemeData(hTheme);
                        base.BackColor = ColorTranslator.FromWin32(pColor);
                    }
                }
        }

        #endregion

        #region - APIs -

        const string VSCLASS_TAB = "TAB";

        const int TABP_TABITEM = 1;
        const int TABP_TABITEMLEFTEDGE = 2;
        const int TABP_TABITEMRIGHTEDGE = 3;
        const int TABP_TABITEMBOTHEDGE = 4;
        const int TABP_TOPTABITEM = 5;
        const int TABP_TOPTABITEMLEFTEDGE = 6;
        const int TABP_TOPTABITEMRIGHTEDGE = 7;
        const int TABP_TOPTABITEMBOTHEDGE = 8;
        const int TABP_PANE = 9;
        const int TABP_BODY = 10;
        const int TABP_AEROWIZARDBODY = 11;

        const int TIS_NORMAL = 1;

        const int TMT_FILLCOLOR = 3802;
        const int TMT_EDGELIGHTCOLOR = 3804;
        const int TMT_EDGEHIGHLIGHTCOLOR = 3805;
        const int TMT_EDGESHADOWCOLOR = 3806;
        const int TMT_EDGEDKSHADOWCOLOR = 3807;
        const int TMT_EDGEFILLCOLOR = 3808;
        const int TMT_GLOWCOLOR = 3816;
        const int TMT_FILLCOLORHINT = 3821;

        public enum NotifyResult {
            NoError = 0,
            Invalid = 1,
            InvalidNoChangePage = 2
        }
        public enum WindowStyles {
            WS_VISIBLE = 0x10000000,
            WS_TABSTOP = 0x10000
        }
        public enum WindowMessages {
            WM_SIZE = 5,
            WM_PAINT = 0xF,
            WM_NOTIFY = 0x4E,
            WM_INITDIALOG = 0x110,
            PSM_CHANGED = 0x400 + 104,
            PSM_UNCHANGED = 0x400 + 109,
            PSM_APPLY = 0x400 + 110,
            PSM_CANCELTOCLOSE = 0x400 + 107
        }

        [DllImport("user32.dll")]
        public extern static IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
        [DllImport("user32.dll")]
        public extern static IntPtr GetParent(IntPtr hwnd);
        [DllImport("user32.dll", CharSet = CharSet.Ansi, EntryPoint = "SendMessageA")]
        public extern static int SendMessage(IntPtr hwnd, int Msg, IntPtr wParam, IntPtr lParam);
        [DllImport("comctl32.dll", CharSet = CharSet.Unicode)]
        extern static IntPtr CreatePropertySheetPageW(ref PROPSHEETPAGE PSP);
        [DllImport("comctl32.dll")]
        public extern static int DestroyPropertySheetPage(IntPtr hPage);
        [DllImport("user32.dll")]
        public extern static int GetClientRect(IntPtr hWnd, ref Rectangle r);
        [DllImport("uxtheme.dll")]
        public extern static bool IsThemeActive();
        [DllImport("uxtheme.dll")]
        extern static int DrawThemeBackground(
           IntPtr hTheme,
           IntPtr hDc,
           int iPartId,
           int iStateId,
           ref Rectangle pRect,
           ref Rectangle pClipRect);
        [DllImport("uxtheme.dll")]
        extern static int GetThemeColor(
           IntPtr hTheme,
           int iPartId,
           int iStateId,
           int iPropId,
           [Out] out int pColor);
        [DllImport("uxtheme.dll", CharSet = CharSet.Unicode)]
        extern static IntPtr OpenThemeData(IntPtr hwnd, [In, MarshalAs(UnmanagedType.LPWStr)] string pszClassList);
        [DllImport("uxtheme.dll")]
        extern static IntPtr CloseThemeData(IntPtr hTheme);

        #endregion

        #region - 公共属性 -

        [Browsable(false)]
        public FileCollection Files { get { return m_Files; } }
        [Browsable(false)]
        public IntPtr DialogHandle { get { return m_DlgHandle; } }
        [Browsable(false)]
        public IntPtr SheetHandle { get { return m_SheetHandle; } }
        [DefaultValue(typeof(Size), "341, 408")]
        public new Size Size {
            get { return base.Size; }
            set { base.Size = value; }
        }
        //[DefaultValue(typeof(Color), "-1")]
        //public override Color BackColor {
        //    get { return base.BackColor; }
        //    set { base.BackColor = value; }
        //}
        [Category("Property Sheet")]
        public bool ShowHelpButton {
            get { return m_ShowHelpButton; }
            set { m_ShowHelpButton = value; }
        }
        [Category("Property Sheet")]
        public Bitmap Icon {
            get { return m_Icon; }
            set { m_Icon = value; }
        }

        #endregion

        #region - 公共方法 -

        public void Changed() {
            PropertySheetBase.SendMessage(m_DlgHandle, (int)WindowMessages.PSM_CHANGED, m_SheetHandle, IntPtr.Zero);
        }
        public void Reset() {
            PropertySheetBase.SendMessage(m_DlgHandle, (int)WindowMessages.PSM_UNCHANGED, m_SheetHandle, IntPtr.Zero);
        }
        public void Apply() {
            PropertySheetBase.SendMessage(m_DlgHandle, (int)WindowMessages.PSM_APPLY, m_SheetHandle, IntPtr.Zero);
        }
        public void CancelToClose() {
            PropertySheetBase.SendMessage(m_DlgHandle, (int)WindowMessages.PSM_CANCELTOCLOSE, IntPtr.Zero, IntPtr.Zero);
        }

        #endregion

        #region - 子类重写 -

        [Browsable(false)]
        protected virtual bool AllowMultiFiles { get { return false; } }
        [Browsable(false)]
        protected virtual string[] AllowExtensions { get { return new string[0]; } }

        protected virtual void OnCreatePage() { }
        protected virtual NotifyResult OnCancel() { return NotifyResult.NoError; }
        protected virtual NotifyResult OnActivate() { return NotifyResult.NoError; }
        protected virtual NotifyResult OnApply() { return NotifyResult.NoError; }
        protected virtual NotifyResult OnShowHelp() { return NotifyResult.NoError; }

        protected override void OnPaintBackground(PaintEventArgs e) {
            base.OnPaintBackground(e);
            if (m_IsXP && PropertySheetBase.IsThemeActive()) {
#if DEBUG
                Common.OutputDebugString("OnPaintBackground(PaintEventArgs):: Draw XP visual styles.");
#endif
                IntPtr hTheme = PropertySheetBase.OpenThemeData(this.Handle, VSCLASS_TAB);
                IntPtr hDC = e.Graphics.GetHdc();
                Rectangle rect = this.ClientRectangle;
                PropertySheetBase.DrawThemeBackground(hTheme, hDC, TABP_BODY, TIS_NORMAL, ref rect, ref rect);
                e.Graphics.ReleaseHdc(hDC);
                PropertySheetBase.CloseThemeData(hTheme);
            }
        }

        #endregion

        #region - 私有方法 -

        private bool Callback(IntPtr hWnd, PSPCALLBACKMESSAGES Msg, ref PROPSHEETPAGE Page) {
            switch (Msg) {
                case PSPCALLBACKMESSAGES.PSPCB_CREATE:
#if DEBUG
                    Common.OutputDebugString("Callback(IntPtr, PSPCALLBACKMESSAGES, ref PROPSHEETPAGE):: PSPCB_CREATE.");
#endif
                    break;

                case PSPCALLBACKMESSAGES.PSPCB_RELEASE:
#if DEBUG
                    Common.OutputDebugString("Callback(IntPtr, PSPCALLBACKMESSAGES, ref PROPSHEETPAGE):: PSPCB_RELEASE");
#endif
                    Marshal.FreeCoTaskMem(m_PropSheet.pResource);
                    if (m_Icon != null) {
                        m_Icon.Dispose();
                        m_Icon = null;
                    }
                    m_Files.Dispose();
                    m_Files = null;
                    this.Dispose();
                    GC.SuppressFinalize(this);
                    GC.Collect();
                    break;
            }
            return true;
        }
        private int PageProc(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam) {
            switch (Msg) {
                case (int)WindowMessages.WM_INITDIALOG:
#if DEBUG
                    Common.OutputDebugString("PageProc(IntPtr, int, IntPtr, IntPtr):: WM_INITDIALOG.");
#endif
                    PropertySheetBase.SetParent(this.Handle, hWnd);
                    m_SheetHandle = hWnd;
                    m_DlgHandle = PropertySheetBase.GetParent(hWnd);
                    this.Visible = true;
                    return 1;

                case (int)WindowMessages.WM_NOTIFY:
                    PSHNOTIFY notify = (PSHNOTIFY)Marshal.PtrToStructure(lParam, typeof(PSHNOTIFY));
                    switch (notify.code) {
                        case PSNOTIFYCODES.PSN_SETACTIVE:
#if DEBUG
                            Common.OutputDebugString("PageProc(IntPtr, int, IntPtr, IntPtr):: PSN_SETACTIVE.");
#endif
                            return (int)this.OnActivate();

                        case PSNOTIFYCODES.PSN_APPLY:
#if DEBUG
                            Common.OutputDebugString("PageProc(IntPtr, int, IntPtr, IntPtr):: PSN_APPLY.");
#endif
                            return (int)this.OnApply();

                        case PSNOTIFYCODES.PSN_RESET:
#if DEBUG
                            Common.OutputDebugString("PageProc(IntPtr, int, IntPtr, IntPtr):: PSN_RESET.");
#endif
                            return (int)this.OnCancel();

                        case PSNOTIFYCODES.PSN_HELP:
#if DEBUG
                            Common.OutputDebugString("PageProc(IntPtr, int, IntPtr, IntPtr):: PSN_HELP.");
#endif
                            return (int)this.OnShowHelp();

                    }
                    return 1;

                case (int)WindowMessages.WM_SIZE:
                    int i = lParam.ToInt32();
                    this.Width = i & 0xFFFF;
                    this.Height = i / 0x10000;
#if DEBUG
                    Common.OutputDebugString("PageProc(IntPtr, int, IntPtr, IntPtr):: WM_SIZE, Width = {0}, Height = {1}", this.Width, this.Height);
#endif
                    this.OnCreatePage();
                    return 0;
            }
            return 0;
        }

        #endregion

        #region - IShellPropSheetExt 成员 -

        int IShellPropSheetExt.AddPages([MarshalAs(UnmanagedType.FunctionPtr)] AddPageDelegate lpfnAddPage, IntPtr lParam) {
            if (m_Files.Count < 1) {
                Common.OutputDebugString("AddPages(delegate, IntPtr):: failed... FileCollection.Count = 0");
                return 1;
            } else if (m_Files.Count > 1) {
                if (this.AllowMultiFiles) {
                    if (this.AllowExtensions.Length > 0) {
                        foreach (string file in m_Files) {
                            bool allow = false;
                            foreach (string ext in this.AllowExtensions) {
                                if (file.ToLower().EndsWith(ext)) {
                                    allow = true;
                                    break;
                                }
                            }
                            if (!allow) {
                                Common.OutputDebugString("AddPages(delegate, IntPtr):: failed... not allow FileType[{0}]", file.Substring(file.LastIndexOf('.')));
                                return 1;
                            }
                        }
                    }
#if DEBUG
                    Common.OutputDebugString("AddPages(delegate, IntPtr):: start. (MultiFiles)");
#endif
                } else {
                    Common.OutputDebugString("AddPages(delegate, IntPtr):: failed... not allow MultiFiles");
                    return 1;
                }
#if DEBUG
            } else {
                Common.OutputDebugString("AddPages(delegate, IntPtr):: start.");
#endif
            }
            try {
                IntPtr hPage;
                DLGTEMPLATE template = default(DLGTEMPLATE);
                m_PropSheet.dwSize = Marshal.SizeOf(m_PropSheet);
                m_PropSheet.dwFlags = PSP_FLAGS.PSP_USETITLE | PSP_FLAGS.PSP_USECALLBACK | PSP_FLAGS.PSP_DLGINDIRECT;
                if (this.RightToLeft == RightToLeft.Yes)
                    m_PropSheet.dwFlags |= PSP_FLAGS.PSP_RTLREADING;

                if (m_Icon != null) {
                    m_PropSheet.dwFlags |= PSP_FLAGS.PSP_USEHICON;
                    m_PropSheet.hIcon = m_Icon.GetHicon();
                }
                if (m_ShowHelpButton)
                    m_PropSheet.dwFlags |= PSP_FLAGS.PSP_HASHELP;

                template.Style = (int)(WindowStyles.WS_VISIBLE | WindowStyles.WS_TABSTOP);
                m_PropSheet.pResource = Marshal.AllocCoTaskMem(Marshal.SizeOf(template));
                Marshal.StructureToPtr(template, m_PropSheet.pResource, false);

                m_PropSheet.pfnDlgProc = new WndProc(this.PageProc);
                m_PropSheet.pfnCallback = new PropertySheetCallback(this.Callback);
                m_PropSheet.pszTitle = this.Text;

                hPage = PropertySheetBase.CreatePropertySheetPageW(ref m_PropSheet);

                if (hPage.ToInt32() != 0) {
                    if (!lpfnAddPage(hPage, lParam)) {
                        Common.OutputDebugString("AddPages(delegate, IntPtr):: failed. 'lpfnAddPage' returns false.");
                        PropertySheetBase.DestroyPropertySheetPage(hPage);
                    }
#if DEBUG
 else Common.OutputDebugString("AddPages(delegate, IntPtr):: OK.");
#endif
                }
                return 0;
            } catch (Exception e) {
                Common.OutputDebugString("AddPages(delegate, IntPtr):: exception - ", e.Message);
            }
            return 1;
        }
        void IShellPropSheetExt.ReplacePage(uint uPageID, IntPtr lpfnReplaceWith, IntPtr lParam) { }

        #endregion

        #region - IShellExtInit 成员 -

        void IShellExtInit.Initialize(IntPtr pidlFolder, IDataObject lpIDataObject, uint hkeyProgID) {
            m_Files = new FileCollection(lpIDataObject);
#if DEBUG
            Common.OutputDebugString("Initialize(int, IDataObject, int):: OK.");
#endif
        }

        #endregion

        #region - COM 注册 -

        [System.Runtime.InteropServices.ComRegisterFunction()]
        private static void ComReg(Type t) {
            if (t == typeof(PropertySheetBase))
                return;
            Common.RegisterExtension(t, "PropertySheetHandlers\\" + t.FullName);
        }

        [System.Runtime.InteropServices.ComUnregisterFunction()]
        private static void ComUnreg(Type t) {
            if (t == typeof(PropertySheetBase))
                return;
            Common.UnregisterExtension(t, "PropertySheetHandlers\\" + t.FullName);
        }

        #endregion

    }
}
